package org.rambadger.command.auth;


import com.fasterxml.jackson.annotation.JsonProperty;
import org.mindrot.jbcrypt.BCrypt;
import org.mongojack.Id;
import org.mongojack.MongoCollection;

import javax.validation.constraints.NotNull;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * AuthUser provides information about access permissions on the current logged in user.
 *
 * <strong>Never</strong> send this class a JSON response
 */
@MongoCollection(name = "auth")
public final class AuthUser {
    @Id @NotNull
    @JsonProperty private String username;

    @NotNull
    @JsonProperty private String passwordHash;

    @JsonProperty private boolean hasAccess;
    @JsonProperty private boolean hasSystemAdmin;

    @JsonProperty private HashMap<String, EnumSet<PlatformRole>> roles;

    AuthUser() {}

    /**
     * Used by {@link AuthListener} when creating a new entry.
     *
     * @param username the username
     * @param passwordHash the password hash
     */
    AuthUser(String username, String passwordHash) {
        this.username = username;
        this.passwordHash = passwordHash;
        this.hasAccess = false;
        this.hasSystemAdmin = false;
        this.roles = new HashMap<>();
    }


    /**
     * @return whether the user has general view access
     */
    public boolean hasAccess() {
        return hasSystemAdmin || hasAccess;

    }

    /**
     * Indicates whether the user was granted global admin rights. This role implicitly grants all other roles.
     *
     * @return whether the user was granted global admin rights
     */
    public boolean hasAdmin() {
        return hasSystemAdmin;

    }

    /**
     * Returns whether or not the user possesses the specified role for that platform
     * <p>
     *     If the user has global admin rights, this method always returns true. <br/>
     *     If the user has the platform level admin role for the specified platform, they implicitly receive all other platform level roles for that platform.
     * </p>
     * @param platform the platform being check
     * @param role the role
     * @return whether the user has that role
     */
    public boolean hasPlatformRole(String platform, PlatformRole role) {
        checkNotNull(platform);
        checkNotNull(role);

        if (hasSystemAdmin) {
            return true;
        } else if (!hasAccess) {
            return false;
        } else {
            Collection<PlatformRole> activeRoles = roles.get(platform);
            if (activeRoles == null) {
                return false;
            } else if (activeRoles.contains(PlatformRole.CONFIGURE)) {
                return true;
            } else {
                return activeRoles.contains(role);
            }
        }
    }


    /**
     * @return the username of the logged in user.
     */
    public String getUsername() {
        return username;
    }

    /**
     * Checks the password against the user's stored password hash.
     *
     * @param rawPassword the un-hashed password to be checked
     * @return whether the password matches
     */
    public boolean checkPassword(String rawPassword) {
        return BCrypt.checkpw(rawPassword, passwordHash);
    }

    //These are to remain non-public

    /**
     * Grants a platform level role to the user
     *
     * @param platform the platform the role is being granted for
     * @param role the role being granted
     */
    void addPlatformRole(String platform, PlatformRole role) {
        checkNotNull(platform);
        checkNotNull(role);
        EnumSet<PlatformRole> perPlatform;
        if (roles.containsKey(platform)) {
            perPlatform = roles.get(platform);
        } else {
            perPlatform = EnumSet.noneOf(PlatformRole.class);
            roles.put(platform, perPlatform);
        }

        if (!perPlatform.contains(role)) {
            perPlatform.add(role);
        }
    }


    /**
     * Revokes a platform level role.
     *
     * @param platform the platform that the role being revoked belongs to
     * @param role the role being revoked
     */
    void removePlatformRole(String platform, PlatformRole role) {
        checkNotNull(platform);
        checkNotNull(role);
        if (roles.containsKey(platform)) {
            EnumSet<PlatformRole> perPlatform = roles.get(platform);
            perPlatform.remove(role);
            if (perPlatform.isEmpty()) {
                roles.remove(platform);
            }
        }
    }

    /**
     * @return Returns the password hash
     */
    String getPasswordHash() {
        return passwordHash;
    }

    /**
     * Sets a new password hash.
     *
     * @param passwordHash the new password hash
     */
    void setPasswordHash(String passwordHash) {
        this.passwordHash = passwordHash;
    }


    /**
     * Sets whether the user has general view access.
     *
     * @param hasAccess the new setting
     */
    void setHasAccess(boolean hasAccess) {
        this.hasAccess = hasAccess;
    }

    /**
     * Sets whether the user has system level admin access.
     * @param hasSystemAdmin the new setting
     */
    void setHasSystemAdmin(boolean hasSystemAdmin) {
        this.hasSystemAdmin = hasSystemAdmin;
    }

    /**
     * @return the internal table of granted platform level roles.
     */
    HashMap<String, EnumSet<PlatformRole>> getRoles() {
        return roles;
    }
}
